import Node from './Node';
import Path from './Path';
import countPath from './CountPath';
import store from './Store';
import tool from './Tool';
import zrender from 'zrender';

// Main class
export default class App {
    constructor(props = {}) {
        const { id = 'map', bezier = false } = props;
        this.zr = zrender.init(document.getElementById(id));
        this.dragZrGroup = new zrender.Group();
        this.lineZrGroup = new zrender.Group();
        this.nodeZrGroup = new zrender.Group();
        this.tempLineZrGroup = new zrender.Group();
        this.gdata = null;
        this.odata = null;
        this.bezier = bezier;
        const { clientHeight, clientWidth } = this.zr.dom;

        this.dragZrGroup.origin = [clientWidth / 2, clientHeight / 2];

        this.zr.add(this.dragZrGroup);
    }

    /**
     * @desc when dragging node，update path, this function will return node.id，we can get all path(about source.id or target.id equal to id ) by node.id
     * @param {string} id
     * @param {object} gdata
     */
    _updatePath(id) {
        let arr = tool.getLineById(id, this.gdata);
        if (!arr) {
            return;
        }
        // reset points
        arr.forEach(d => {
            d.points = countPath.countLinePoints(d, this.zr);
        });
        this.lineZrGroup.children().forEach(d => {
            d.data.instance.rePath();
        });

        this.seekCallback && this.seekCallback(this.gdata);
    }

    /**
     * @desc path contextmenu
     */
    _onPathContextMenu(e) {
        if (this.pathContextMenuBack) {
            this.pathContextMenuBack(e);
        }
    }

    /**
     * @desc node contextmenu
     */
    _onNodeContextMenu(e) {
        if (this.nodeContextMenuBack) {
            this.nodeContextMenuBack(e);
        }
    }

    /**
     * @desc drag node
     */
    _dragZr(event) {
        if (event) {
            if (!event.target) {
                tool.dragGroup(event, this.dragZrGroup);
            }
        } else {
            this.zr.off('mousedown');
            this.zr.on('mousedown', e => {
                // if (!e.target) {
                tool.dragGroup(e, this.dragZrGroup, () => {
                    this.seekZrCallback && this.seekZrCallback(this.dragZrGroup);
                });
                // }
            });
        }
        document.addEventListener('contextmenu', e => {
            e.preventDefault();
        });
    }

    /**
     * @desc 缩放
     */
    _scaleZr() {
        this.zr.off('mousewheel');
        this.zr.on('mousewheel', e => {
            e.event.preventDefault();
            let [scaleX, scaleY] = this.dragZrGroup.scale;
            let k = 0.2;
            if (e.wheelDelta < 0) {
                scaleX -= k;
                scaleY -= k;
            } else {
                scaleX += k;
                scaleY += k;
            }
            if (scaleX < 0.2) {
                scaleX = 0.2;
                scaleY = 0.2;
            }
            this.dragZrGroup.attr({
                scale: [scaleX, scaleY]
            });
            this.seekZrCallback && this.seekZrCallback(this.dragZrGroup);
        });
    }

    // /**
    //  * @desc mousedown event drag node
    //  */
    // dragNode(e) {
    //     this._dragZr(e);
    // }

    /**
     * @desc add new path
     * @param {object} data {
        from: 1,
        fromdire: 'right',
        to: 2,
        todire: 'left',
        type: 1
    }
     */
    addPath(data) {
        const path = { type: 1, id: tool.getRandomID() };
        path.from = data.start.data.id;
        path.to = data.end.data.id;
        path.fromdire = data.start.shape.dirc;
        path.todire = data.end.shape.dirc;
        this.saveState();
        this.odata = store.add(this.odata, { nodes: [], links: [path] });
        this.clear();
        this.init(this.odata, true);
    }

    /**
     * @desc add new node
     * @param {object} data {
        id: 3,
        width: 80,
        height: 100,
        fill: '#0ff',
        x: 500,
        y: 300,
        data: null
    }
     */
    addNode(data) {
        if (!data.id) {
            data.id = tool.getRandomID();
        }
        this.saveState();
        this.odata = store.add(this.odata, { nodes: [data], links: [] });
        this.clear();
        this.init(this.odata, true);
    }

    /**
     * @desc delete path id
     */
    delPath(id) {
        this.saveState();
        this.odata = store.del(this.odata, { nodes: [], links: [id] });
        this.clear();
        this.init(this.odata, true);
    }

    /**
     * @desc delete node
     */
    delNode(id) {
        this.saveState();
        this.odata = store.del(this.odata, tool.delById(id, this.gdata));
        this.clear();
        this.init(this.odata, true);
    }

    /**
     * @desc save prev state
     */
    saveState() {
        const { links, nodes } = this.odata;
        const { nodesKV, linksKV } = this.gdata;
        links.map(d => {
            const { from, fromdire, to, todire, type } = linksKV[d.id];
            d.from = from;
            d.fromdire = fromdire;
            d.todire = todire;
            d.to = to;
            d.type = type;
            return d;
        });
        nodes.map(d => {
            const { width, height, fill, x, y, data } = nodesKV[d.id];
            d.width = width;
            d.height = height;
            d.fill = fill;
            d.x = x;
            d.y = y;
            d.data = JSON.parse(JSON.stringify(data));
        });
        return this.odata;
    }

    /**
     * @desc draw line
     */
    drawPath() {
        const { links, nodes } = this.gdata;
        links.forEach(d => {
            if (!d.instance) {
                // d.instance = new Path(d, this._onPathContextMenu.bind(this));
                d.instance = new Path(d, this._onPathContextMenu.bind(this), this.bezier);
                this.lineZrGroup.add(d.instance.getPath());
            }
        });
        this.dragZrGroup.add(this.lineZrGroup);
    }

    /**
     * @desc draw node
     */
    drawNodes() {
        const { nodes } = this.gdata;
        nodes.forEach(d => {
            const path = new Node(
                d,
                this.tempLineZrGroup,
                this._updatePath.bind(this),
                this.addPath.bind(this),
                this._onNodeContextMenu.bind(this),
                this.dragZrGroup
            );
            this.nodeZrGroup.add(path.getNode());
        });
        this.dragZrGroup.add(this.nodeZrGroup);
        this.zr.add(this.tempLineZrGroup);
    }

    /**
     * @desc path contextMenu
     */
    pathContextMenu(callback) {
        if (callback) {
            this.pathContextMenuBack = callback;
        }
    }

    /**
     * @desc node contextMenu
     */
    nodeContextMenu(callback) {
        if (callback) {
            this.nodeContextMenuBack = callback;
        }
    }

    /**
     * @desc on events
     * @param {string} eventName
     * @param {function} callback
     */
    on(eventName, callback) {
        this.zr.on(eventName, callback);
    }

    /**
     * @desc off events
     * @param {string} eventName
     * @param {function} callback
     */
    off(eventName, callback) {
        this.zr.off(eventName, callback);
    }

    clear() {
        this.dragZrGroup.removeAll();
        this.lineZrGroup.removeAll();
        this.nodeZrGroup.removeAll();
        this.tempLineZrGroup.removeAll();
        this.zr.off('mousedown');
    }

    /**
     * @desc seek data
     */
    seek = callback => {
        this.seekCallback = callback;
    };

    /**
     * @desc box seek
     */
    seekZr = callback => {
        this.seekZrCallback = callback;
    };

    /**
     * @desc init class
     * @param {object} data { nodes, links }
     */
    init(data, noDrag) {
        this.odata = store.filterData(data);
        this.gdata = tool.toGridData(data);
        this.drawNodes();
        this.drawPath();
        this._dragZr();
        this._scaleZr();
    }
}
